package no.sogl.domain;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Enumerated;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;

import no.sogl.reference.CommunicationType;
import no.sogl.reference.Gender;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@RooJavaBean
@RooToString
@RooJpaActiveRecord(finders = { "findPeopleByLastNameLikeAndBirthDayEquals", "findPeopleByBirthDayEquals",
        "findPeopleByFirstNameLikeAndLastNameLike", "findPeopleByBirthYearEquals", "findPeopleByBirthYearBetween" })
public class Person {

    @NotNull
    @Size(min = 3, max = 30)
    private String firstName;

    @NotNull
    @Size(min = 2)
    private String lastName;

    @Size(max = 50)
    private String address;

    @Size(max = 50)
    private String address2;

    @Size(min = 4, max = 4)
    private String zipCode;

    @Size(max = 30)
    private String city;

    @Size(max = 255)
    private String phone;

    @Size(max = 255)
    private String workPhone;

    @Size(max = 255)
    private String mobile;

    @Size(max = 255)
    private String email;

    @Past
    @Temporal(TemporalType.DATE)
    @DateTimeFormat(style = "S-")
    private Date birthDay;

    @Max(2050)
    @Min(1900)
    private Integer birthYear;

    @NotNull
    private boolean approveOnlinePictures = false;

    @OneToOne(orphanRemoval = true, optional = true, cascade = CascadeType.ALL, mappedBy = "person")
    private Membership membership;

    @Enumerated
    private CommunicationType preferredCommunicationType;

    @Size(max = 2000)
    private String comment;

    @Size(max = 255)
    private String parentNames;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(style = "S-")
    @Column(updatable = false, insertable = true)
    private Date createdAt = new Date();

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(style = "SS")
    private Date updatedAt = new Date();

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "person")
    private Set<PersonTag> tags = new HashSet<PersonTag>();

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    private Set<Person> children = new HashSet<Person>();

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    private Set<Person> parents = new HashSet<Person>();

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "coach")
    private Set<TeamCoach> coachOfTeams = new HashSet<TeamCoach>();

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "contact")
    private Set<TeamContact> contactOfTeams = new HashSet<TeamContact>();

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "member")
    private Set<TeamMember> teamMemberships = new HashSet<TeamMember>();

    @Enumerated
    private Gender gender;

    public void addTag(String tagName) {
        if (!StringUtils.hasText(tagName)) return;
        Tag tag = Tag.findTagByNameEquals(tagName);
        if (tag == null) return;
        addTag(tag);
    }

    public void addTag(Tag tag) {
        if (tag == null) return;
        for (PersonTag pt : getTags())
            if (pt.getTag().equals(tag)) return;
        PersonTag pt = new PersonTag(this, tag);
        if (getTags().add(pt)) merge();
    }

    public void removeTag(String tagName) {
        if (!StringUtils.hasText(tagName)) return;
        Tag tag = Tag.findTagByNameEquals(tagName);
        if (tag == null) return;
        PersonTag ptToRemove = null;
        for (PersonTag pt : getTags()) {
            if (pt.getTag().equals(tag)) {
                ptToRemove = pt;
                break;
            }
        }
        if (ptToRemove == null) return;
        if (getTags().remove(ptToRemove)) merge();
    }

    @Transient
    public void setFullName(String name) {
        String[] firstNameLastName = splitFullName(name);
        lastName = firstNameLastName[1];
        firstName = firstNameLastName[0];
    }

    @Transient
    public String getLongInitials() {
        if ( birthDay != null ) return initialize(firstName, lastName, birthDay);
        if ( birthYear != null ) return initialize(firstName, lastName, birthYear);
        return initialize(firstName, lastName);
    }
    @Transient
    public String getLongInitialsWithBirthYear() {
        if ( birthYear != null ) return initialize(firstName, lastName, birthYear);
        return initialize(firstName, lastName);
    }

    public static String initialize(String firstName, String lastName, int yearOfBirth) {
        return initialize(firstName, lastName) + yearOfBirth;
    }

    public static String initialize(String firstName, String lastName, Date dateOfBirth) {
        return initialize(firstName, lastName)
                + org.joda.time.format.DateTimeFormat.forPattern("ddMMyyyy").print(dateOfBirth.getTime());
    }

    public static String initialize(String firstName, String lastName) {
        String[] parts = (firstName + " " + lastName).split("[ -]+");
        if ( parts.length == 1) return org.apache.commons.lang3.StringUtils.substring(parts[0], 0, 2).toUpperCase();
        return org.apache.commons.lang3.StringUtils.substring(parts[0], 0, 2).toUpperCase() + org.apache.commons.lang3.StringUtils.substring(parts[parts.length -1], 0, 2).toUpperCase();
    }

    public boolean hasTag(String tagName) {
        if (!StringUtils.hasText(tagName)) return false;
        Tag tag = Tag.findTagByNameEquals(tagName);
        if (tag == null) return false;
        for (PersonTag pt : getTags())
            if (pt.getTag().equals(tag)) return true;
        return false;

    }

    public static String[] splitFullName(String name) {
        String[] nameParts = name.split("[ ,]+");
        if (nameParts.length == 1) return new String[] { name, "" };
        String lastName = nameParts[nameParts.length - 1];
        String firstName = null;
        for (int i = 0; i < nameParts.length - 1; i++) {
            firstName = firstName == null ? nameParts[i] : firstName + " " + nameParts[i];
        }
        return new String[] { firstName, lastName };
    }

    @PrePersist
    @PreUpdate
    public void onUpdate() {
        updatedAt = new Date();
        if (!CollectionUtils.isEmpty(parents)) {
            for (Person person : parents) {
                if (!person.getChildren().contains(this)) person.getChildren().add(this);
            }
        }
        if (!CollectionUtils.isEmpty(children)) {
            for (Person person : children) {
                if (!person.getParents().contains(this)) person.getParents().add(this);
            }
        }
    }

    @PreRemove
    public void onDelete() {
        for (Person child : children)
            child.getParents().remove(this);
        for (Person parent : parents)
            parent.getChildren().remove(this);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Address: ").append(getAddress()).append(", ");
        sb.append("BirthDay: ").append(getBirthDay()).append(", ");
        sb.append("Children: ").append(getChildren() == null ? "null" : getChildren().size()).append(", ");
        sb.append("City: ").append(getCity()).append(", ");
        sb.append("CoachOfTeams: ").append(getCoachOfTeams() == null ? "null" : getCoachOfTeams().size()).append(", ");
        sb.append("Comment: ").append(getComment()).append(", ");
        sb.append("ContactOfTeams: ").append(getContactOfTeams() == null ? "null" : getContactOfTeams().size()).append(", ");
        sb.append("CreatedAt: ").append(getCreatedAt()).append(", ");
        sb.append("Email: ").append(getEmail()).append(", ");
        sb.append("FirstName: ").append(getFirstName()).append(", ");
        sb.append("Gender: ").append(getGender()).append(", ");
        sb.append("Id: ").append(getId()).append(", ");
        sb.append("LastName: ").append(getLastName()).append(", ");
        sb.append("Mobile: ").append(getMobile()).append(", ");
        sb.append("Parents: ").append(getParents() == null ? "null" : getParents().size()).append(", ");
        sb.append("Phone: ").append(getPhone()).append(", ");
        sb.append("PreferredCommunicationType: ").append(getPreferredCommunicationType()).append(", ");
        sb.append("Tags: ").append(getTags() == null ? "null" : getTags().size()).append(", ");
        sb.append("TeamMemberships: ").append(getTeamMemberships() == null ? "null" : getTeamMemberships().size()).append(", ");
        sb.append("UpdatedAt: ").append(getUpdatedAt()).append(", ");
        sb.append("Version: ").append(getVersion()).append(", ");
        sb.append("ZipCode: ").append(getZipCode()).append(", ");
        sb.append("ApproveOnlinePictures: ").append(isApproveOnlinePictures()).append(", ");
        return sb.toString();
    }
}
